home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Interactive Web Graphics with Shout 3D
/
Interactive Web Graphics With Shout 3D.iso
/
pc
/
Shout3Ddemo
/
Shout3d_runtime
/
codebase
/
applets
/
ExaminePanel.java
< prev
next >
Wrap
Text File
|
2000-11-10
|
25KB
|
674 lines
/**
Company: Eyematic Interfaces
Project: Shout3D 2.0 Sample Code
Class: ExaminePanel
Date: September 15, 1999
Description: Class for ExaminePanel
(C) Copyright Eyematic Interfaces, Inc. - 1997-2000 - All rights reserved
*/
package applets;
import shout3d.core.*;
import shout3d.math.*;
import shout3d.*;
/**
* Shout3D ExaminePanel. This class provides the user with the ability to
* examine an item. It is similar
* to but not exactly like, VRML "EXAMINE" mode.
*
* Basic Controls:
* -- rot = drag
* -- pan = <shift>+drag
* -- zoom = <control>+drag
*
* Drag Styles
* Mouse motion is interpreted via one of two dragStyles.
* The dragStyle may be changed with an applet parameter.
* -- "by speed" changes values over time by a velocity.
* The velocity is proportional to how far the mouse has been moved
* since mouse down.
* -- "by offset" changes the value once per mouse-movement.
* The value is saved upon mouse-down, and with each move, the new
* value is set to be the initial value plus an offset that is
* proportional to how far the mouse has been moved since mouse down.
*
* Rotation Center.
* -- by default, the camera rotates about the center of the scene's bounding box.
* -- the rotationCenter applet parameter lets you change this center.
*
* Framing the Scene:
* -- The starting distance from the camera to the rotation center is calculated
* to include the entire scene within frame limits, within an offset of backupSlack
* to allow adjustment. See the backupSlack applet parameter below.
*
* Cameras & Scenes:
* -- Works fine if you bind a new camera or change scenes.
* -- resetCamera() method returns to the original setting of the camera.
*
* Applet Parameters:
* -- dragStyle (default = "by speed")
* Values are "by speed" or "by offset", corresponding to the drag styles described above
* -- rotationCenter (default = undefined, meaning use scene bbox center)
* If present, the parameter should be 3 floats, for example: "0 0 0"
* If present, rotation is about this worldspace point instead of the scene's bbox center
* -- backupSlack (default = 0.1)
* For framing the scene in the panel.
* If 0, then the scene will fit the frame as exactly as possible.
* If > 0, then the camera will be backed up further to show more. The value is multiplied
* by the original framing distance to get a new one. For example, if a distance of 10 frames
* perfectly, then a backupSlack of 0.1 means that the new distance will be 11.
* -- bySpeedZoomDragFactor (default = .05)
* -- bySpeedPanDragFactor (default = .025)
* -- bySpeedRotateDragFactor (default = .033)
* The above three parameters only apply when dragStyle is "by speed." They specify the
* amount of zoom, pan, or rotate velocity per pixel moved. For example, dragging 100 pixels
* in zoom mode moves the camera at 5 units/second.
* -- byOffsetZoomDragFactor (default = .01)
* -- byOffsetPanDragFactor (default = .005)
* -- byOffsetRotateDragFactor (default = .016)
* The above three parameters only apply when dragStyle is "by offset." They specify the
* total change in zoom, pan, or rotation per pixel moved. For example, dragging 100 pixels
* in zoom mode moves the camera by 5 units.
*
* @author Jim Stewartson
* @author Paul Isaacs
* @author Dave Westwood
* @author Rory Lane Lutter
*/
public class ExaminePanel extends Shout3DPanel implements DeviceObserver {
/////////////////////////////////////////////////////////////
// Variables set via applet parameters
//
// See comments at top of file for more info.
/////////////////////////////////////////////////////////////
// Selection of mapping between mouse motion and the camera.
// See comments at top of file.
static public final int BY_SPEED = 0;
static public final int BY_OFFSET = 1;
int currentDragStyle = BY_SPEED;
// Center of rotation. If null, bbox center will be used.
float[] rotationCenter = null;
// For framing the scene.
float backupSlack = 0.1f;
// Speeds for "by speed" dragStyle
float bySpeedZoomDragFactor = .05f;
float bySpeedPanDragFactor = .025f;
float bySpeedRotateDragFactor = .033f;
// Speeds for byOffset dragStyle
float byOffsetZoomDragFactor = .01f;
float byOffsetPanDragFactor = .005f;
float byOffsetRotateDragFactor = .016f;
///////////////////////////////////////////////////////////////////
// Public methods to call to control the behavior
///////////////////////////////////////////////////////////////////
/**
* call this whenever you want to go reset the camera.
* Result is that camera goes back to its initial orientation,
*
* Position is chosen to look at the current center of rotation. In the
* case where objects are moving, this may be different than it was
* when the scene was first loaded.
*/
public void resetCamera(){
wantToReset = true;
}
///////////////////////////////////////////////////////////////////
// Protected member variables used in doing calculations
///////////////////////////////////////////////////////////////////
// The three modes of manipulation for the camera:
static final int PAN = 0;
static final int ROTATE = 1;
static final int ZOOM = 2;
// The mode of manipulation currently in use.
int currentMode;
// cached reference to the initial viewpoint
Viewpoint camera;
Node[] pathToCameraParent = null;
// cached reference to the scene root
Transform root;
// These store the initial cursor location of a click-drag-release sequence.
float startX = 0;
float startY = 0;
// This holds the value of the camera's position relative to the
// center of rotation, prior to rotation about that center.
// The x,y values hold any x or y panning that's been done.
// The z value holds the distance back from the bbox center, modified by
// any zooming that's been done.
float[] unrotatedCameraOffset = new float[3];
// This is what the unrotatedCameraOffset turns into after being rotated
// by the camera's orientation. Add this to the center of rotation to determine
// where the camera should be placed.
float[] rotatedCameraOffset = new float[3];
// These hold values of the camera when the mouse was first pressed
// during a click-drag-release sequence
float[] startUnrotatedCameraOffset = new float[3];
float[] startHeadingPitchRoll = new float[3];
// Used for rotational calculations
Quaternion tempQuat = new Quaternion();
// speed values for manipulating camera
float cameraXPanSpeed = 0;
float cameraYPanSpeed = 0;
float cameraHeadingSpeed = 0;
float cameraPitchSpeed = 0;
float cameraZoomSpeed = 0;
float cameraHeading = 0;
float cameraPitch = -0.2f;
float cameraRoll = 0;
float cameraDist = 0;
float[] bboxMin;
float[] bboxMax;
float[] bboxCenter;
// For resetting the camera when resetCamera() is called.
boolean wantToReset = false;
boolean gotInitCameraOrientation = false;
float[] initCameraOrientation = { 0, 0, 0, 0 };
/**
* Constructs an ExaminePanel
*/
public ExaminePanel(Shout3DApplet applet){
super(applet);
}
/**
* Constructs an ExaminePanel
*
* @param applet the Shout3DApplet in which this panel is to be drawn
* @param width the width of the panel in pixels
* @param height the height of the panel in pixels
*/
public ExaminePanel(Shout3DApplet applet, int width, int height){
super(applet,width,height);
}
/**
* Constructs an ExaminePanel
*
* @param applet the Shout3DApplet in which this panel is to be drawn
* @param x the x position of the panel in pixels
* @param y the x position of the panel in pixels
* @param width the width of the panel in pixels
* @param height the height of the panel in pixels
*/
public ExaminePanel(Shout3DApplet applet, int x, int y, int width, int height){
super(applet,x,y,width,height);
}
/**
* Remove observers when done with the panel
*/
public void finalize()throws Throwable {
applet.getDeviceListener().removeDeviceObserver(this, "DeviceInput");
applet.getRenderer().removeRenderObserver(this);
super.finalize();
}
// a float field being used only to convert the javascript string
//into a float[] in the init rotationCenter param
FloatArrayField rotationCenterField = new FloatArrayField(getScene(), "rotationCenterField", 0, (new float[]{0.0f, 0.0f, 0.0f}));
/**
* Overrides Shout3DPanel.customInitialize()
*/
public void customInitialize() {
camera = (Viewpoint)(applet.getCurrentBindableNode("Viewpoint"));
root = (Transform)getScene();
// Get applet parameters
String valueString;
valueString = applet.getParameter("dragStyle");
if (valueString != null &&
(valueString.equals("by offset") || valueString.equals("by_offset") ||
valueString.equals("BY OFFSET") || valueString.equals("BY_OFFSET"))){
currentDragStyle = BY_OFFSET;
}
valueString = applet.getParameter("rotationCenter");
if (valueString != null){
rotationCenterField.setValueByString(valueString);
rotationCenter = rotationCenterField.getValue();
}
valueString = applet.getParameter("backupSlack");
if (valueString != null){
backupSlack = Float.valueOf(valueString).floatValue();
}
valueString = applet.getParameter("bySpeedZoomDragFactor");
if (valueString != null){
bySpeedZoomDragFactor = Float.valueOf(valueString).floatValue();
}
valueString = applet.getParameter("bySpeedPanDragFactor");
if (valueString != null){
bySpeedPanDragFactor = Float.valueOf(valueString).floatValue();
}
valueString = applet.getParameter("bySpeedRotateDragFactor");
if (valueString != null){
bySpeedRotateDragFactor = Float.valueOf(valueString).floatValue();
}
valueString = applet.getParameter("byOffsetZoomDragFactor");
if (valueString != null){
byOffsetZoomDragFactor = Float.valueOf(valueString).floatValue();
}
valueString = applet.getParameter("byOffsetPanDragFactor");
if (valueString != null){
byOffsetPanDragFactor = Float.valueOf(valueString).floatValue();
}
valueString = applet.getParameter("byOffsetRotateDragFactor");
if (valueString != null){
byOffsetRotateDragFactor = Float.valueOf(valueString).floatValue();
}
// register for device input
applet.getDeviceListener().addDeviceObserver(this, "DeviceInput", null);
// register for rendering notification
applet.getRenderer().addRenderObserver(this, null);
}
/**
* DeviceObserver method
*/
public boolean onDeviceInput(DeviceInput di, Object userData){
if (di instanceof MouseInput){
MouseInput mi = (MouseInput)di;
switch (mi.which){
case MouseInput.DOWN:
onMouseDown(mi);
break;
case MouseInput.DRAG:
onMouseDrag(mi);
break;
case MouseInput.UP:
onMouseUp(mi);
break;
}
}
// return false, let other entities handle the event too.
return false;
}
/**
* Called when the mouse is pressed
*/
void onMouseDown(MouseInput mi){
// Save the initial X and Y positions of the cursor.
startX = mi.x;
startY = mi.y;
// Based on the modifiers, determine the mode of motion:
currentMode = getManipulationMode(mi);
if (currentDragStyle == BY_OFFSET){
// For "by offset" dragging, we need to remember the
// starting unrotatedCameraOffset and the starting euler parameters.
for(int i = 0; i< 3; i++){
startUnrotatedCameraOffset[i] = unrotatedCameraOffset[i];
}
// convert the camera's orientation at the beginning into euler parameters.
tempQuat.setAxisAngle(camera.orientation.getValue());
tempQuat.getEulers(startHeadingPitchRoll);
}
}
/**
* Called when the mouse is dragged
*/
void onMouseDrag(MouseInput mi){
// If the user has changed modes by pressing or releasing a modifier key,
// we need to simulate the ending of the old drag and the beginning of a
// new one in order to get a non-jumpy transition.
// This is easily achieved by calling onMouseUp, then onMouseDown,
// then continuing on as usual.
if (currentMode != getManipulationMode(mi)){
onMouseUp(mi);
onMouseDown(mi);
}
if(currentDragStyle == BY_SPEED){
dragMouseBySpeedStyle(mi);
}
else if (currentDragStyle == BY_OFFSET) {
dragMouseByOffsetStyle(mi);
}
}
/**
* In this dragStyle, velocities are set for each of the types of motion.
* Then, in onPreRender, the velocities are multiplied by time to
* produce a resulting change from frame to frame.
*/
void dragMouseBySpeedStyle(MouseInput mi){
// Start by setting all speeds to 0.
cameraXPanSpeed = 0;
cameraYPanSpeed = 0;
cameraHeadingSpeed = 0;
cameraPitchSpeed = 0;
cameraZoomSpeed = 0;
if (currentMode == ZOOM){
// zoom is based on vertical mouse motion
cameraZoomSpeed = (float)(mi.y - startY)*bySpeedZoomDragFactor;
}
else if (currentMode == PAN){
// pan is based on motion in both directions
cameraXPanSpeed = -(float)(mi.x - startX)*bySpeedPanDragFactor;
cameraYPanSpeed = (float)(mi.y - startY)*bySpeedPanDragFactor;
}
else {
// current mode is ROTATE.
// rotate is based on both directions.
cameraHeadingSpeed = -(float)(mi.x - startX)*bySpeedRotateDragFactor;
cameraPitchSpeed = -(float)(mi.y - startY)*bySpeedRotateDragFactor;
}
}
/**
* In this dragStyle, values for the camera parameters are saved when the
* mouse first is pressed. Then, a change relative to that value is
* created based on how far the mouse has moved during the drag.
*/
void dragMouseByOffsetStyle(MouseInput mi){
if (currentMode == ZOOM){
// zoom is based on vertical mouse motion
unrotatedCameraOffset[2] = startUnrotatedCameraOffset[2] +( (float)(mi.y - startY) )*byOffsetZoomDragFactor;
}
else if (currentMode == PAN){
// pan is based on motion in both directions
unrotatedCameraOffset[0] = startUnrotatedCameraOffset[0]-( (float)(mi.x - startX) )*byOffsetPanDragFactor;
unrotatedCameraOffset[1] = startUnrotatedCameraOffset[1]+( (float)(mi.y - startY) )*byOffsetPanDragFactor;
}
else {
// current mode is ROTATE.
// rotate is based on both directions.
cameraPitch = startHeadingPitchRoll[1] -( (float)(mi.y - startY)*byOffsetRotateDragFactor );
cameraHeading = startHeadingPitchRoll[0] -( (float)(mi.x - startX)*byOffsetRotateDragFactor );
}
}
/**
* Called when the mouse is released
*/
void onMouseUp(MouseInput mi){
// stop rotating the camera
cameraXPanSpeed = 0;
cameraYPanSpeed = 0;
cameraHeadingSpeed = 0;
cameraPitchSpeed = 0;
cameraZoomSpeed = 0;
}
/**
* Gets the manipulation mode based on the modifier keys in the
* MouseInput.
*/
int getManipulationMode(MouseInput mi){
if ((mi.modifiers & DeviceInput.CTRL_MASK) != 0){
// Zoom when control key is down.
return ZOOM;
}
else if ((mi.modifiers & DeviceInput.SHIFT_MASK) != 0){
// Pan when the shift key is down.
return PAN;
}
else {
// Rotate when neither the shift or control key is down.
return ROTATE;
}
}
/**
* Utility function for getting the distance between two points
*/
float getDistance(float[] from, float[] to){
float x = from[0] - to[0];
float y = from[1] - to[1];
float z = from[2] - to[2];
float dist = (float)Math.sqrt(x*x + y*y + z*z);
return dist;
}
/**
* RenderObserver function.
* Does the camera movement calculations after each render.
*
* Note that no onPreRender() method is implemented in this class,
* even though it is required for all classes implementing RenderObserver.
* This is because the super class implements RenderObserver fully
* and onPreRender is inherited.
* Hence this class needs only to override the onPostRender() method,
* making sure to call super.onPostRender(r,userData) within the body
* of the method.
*/
public void onPostRender(Renderer r, Object userData){
super.onPostRender(r,userData);
// If the camera or scene has changed, do necessary initialization.
checkCameraChange();
// Only need to do this in relative mode (i.e., when currentDragStyle == BY_SPEED).
// In "by offset" mode, these five values have been set exactly based on
// the location of the mouse.
if(currentDragStyle == BY_SPEED){
// In relative mode, the location of the mouse gave only a velocity which
// must now be multiplied by elapsed time to give an amount of change that
// is added to the current amount.
// getFramesPerSecond() returns a rate, frames per second.
// Flip this (take 1 divided by this number) to get the number of
// seconds per frame. The number of seconds per frame is the elapsed
// time since we last rendered. Hence...
float timeSinceLastRender = 1/getFramesPerSecond();
// To get how much each of the values should change since the last time
// we rendered a frame, increment each of the 5 parameters
// by its velocity times the timeSinceLastRender.
cameraHeading += cameraHeadingSpeed*timeSinceLastRender;
cameraPitch += cameraPitchSpeed*timeSinceLastRender;
unrotatedCameraOffset[0] += cameraXPanSpeed*timeSinceLastRender;
unrotatedCameraOffset[1] += cameraYPanSpeed*timeSinceLastRender;
unrotatedCameraOffset[2] += cameraZoomSpeed*timeSinceLastRender;
}
// Set the camera orientation based on the current heading, pitch, and roll.
// First, get axis/angle equivalent of the heading/pitch/roll. Axis/angle
// is required to set a rotation field value.
tempQuat.setEulers(cameraHeading, cameraPitch, cameraRoll);
float[] camRot = new float[4];
tempQuat.getAxisAngle(camRot);
// Next, if the result is different from what's there, set the field value.
if (camRot[0] != camera.orientation.getValue()[0] ||
camRot[1] != camera.orientation.getValue()[1] ||
camRot[2] != camera.orientation.getValue()[2] ||
camRot[3] != camera.orientation.getValue()[3]){
camera.orientation.setValue(camRot);
}
// rotate the camera offset by the camera's rotation, still stored
// in tempQuat.
rotatedCameraOffset[0] = unrotatedCameraOffset[0];
rotatedCameraOffset[1] = unrotatedCameraOffset[1];
rotatedCameraOffset[2] = unrotatedCameraOffset[2];
tempQuat.xform(rotatedCameraOffset);
// Add the rotated distance vector to the center of rotation to place the camera.
float[] camPos = new float[3];
camPos[0] = bboxCenter[0]+rotatedCameraOffset[0];
camPos[1] = bboxCenter[1]+rotatedCameraOffset[1];
camPos[2] = bboxCenter[2]+rotatedCameraOffset[2];
if (camPos[0] != camera.position.getValue()[0] ||
camPos[1] != camera.position.getValue()[1] ||
camPos[2] != camera.position.getValue()[2]){
camera.position.setValue(camPos);
}
}
public void checkCameraChange(){
// If scene or camera has changed, re-initialize.
Viewpoint curCam = (Viewpoint)(applet.getCurrentBindableNode("Viewpoint"));
Transform curRoot = (Transform)getScene();
if (curCam != camera || curRoot != root){
camera = curCam;
pathToCameraParent = null;
root = curRoot;
gotInitCameraOrientation = false;
wantToReset = true;
}
// Save the current camera orientation if needed
if (gotInitCameraOrientation == false && camera != null){
System.arraycopy(camera.orientation.getValue(), 0, initCameraOrientation, 0, 4);
gotInitCameraOrientation = true;
}
// Do we want to reset the camera position/orientation?
if (wantToReset == true){
cameraRoll = 0;
// put the camera orientation back.
// Do not pass the reference to initCameraOrientation, or future edits to the
// camera will change our reference as well. Instead, copy the value and call setValue() to
// insure notification.
System.arraycopy(initCameraOrientation, 0, camera.orientation.getValue(), 0, 4);
camera.orientation.setValue( camera.orientation.getValue());
// Setting this to null insures that bbox and camera position will be
// re-initialized below.
bboxMin = null;
// avoid doing this again
wantToReset = false;
}
if (bboxMin == null){
// This is the first render. Establish camera position based on
// starting orientation and bbox size.
// Get the center of the current scene's bbox, in world space
Searcher s = getNewSearcher();
s.setNode(root);
Node[] pathToRoot = s.searchFirst(root);
s.setNode(camera);
Node[] pathToCamera = s.searchFirst(root);
bboxMin = root.getWorldBBoxMin(pathToRoot);
bboxMax = root.getWorldBBoxMax(pathToRoot);
if (bboxMin[0] == 0 &&
bboxMin[1] == 0 &&
bboxMin[2] == 0 &&
bboxMax[0] == 0 &&
bboxMax[1] == 0 &&
bboxMax[2] == 0){
// Scene has not yet calculated a bbox, return and wait until next time.
bboxMin = null;
return;
}
bboxCenter = new float[3];
// Get the path to the camera's parent, if path has length > 1
if (pathToCamera != null && pathToCamera.length > 1){
pathToCameraParent = new Node[pathToCamera.length - 1];
System.arraycopy(pathToCamera, 0, pathToCameraParent, 0, pathToCameraParent.length);
}
// Set the bbox center.
// If a rotationCenter is specified, set the bboxCenter to be the rotationCenter
// and expand the bbox to be centered about the new rotationCenter.
for(int i = 0; i < 3; i++){
if(rotationCenter != null){
// rotation center has been specified. use it as the bounding box
// center and stretch the bounding box to fit.
if(Math.abs(bboxMax[i] - rotationCenter[i])>Math.abs(bboxMin[i] - rotationCenter[i])){
bboxMin[i] = rotationCenter[i]-Math.abs(bboxMax[i] - rotationCenter[i]);
}
else{
bboxMax[i] = rotationCenter[i]+Math.abs(bboxMin[i] - rotationCenter[i]);
}
bboxCenter[i] = rotationCenter[i];
}
else{
// otherwise use the current bounding box to find the center.
bboxCenter[i] = ((bboxMax[i] - bboxMin[i])/2f)+bboxMin[i];
}
}
performInitialCameraPlacement();
// Reset the unrotatedCameraOffset based on the new starting position of the
// camera.
unrotatedCameraOffset[0] = 0f;
unrotatedCameraOffset[1] = 0f;
unrotatedCameraOffset[2] = getDistance(camera.position.getValue(), bboxCenter);
}
}
public void performInitialCameraPlacement() {
// Perform initial camera placement.
// Start at the bbox center:
float[] cameraPos = { bboxCenter[0], bboxCenter[1], bboxCenter[2]};
// Move back far enough that the largest of width/height/depth of the bbox can be fully seen within the field of view
// These are given by the equations: tan(fieldOfView/2) = (width/2)/xbasedZdist;
// tan(fieldOfView/2) = (height/2)/ybasedZdist;
// tan(fieldOfView/2) = (depth/2)/zbasedZdist;
float xbasedZdist = (float)((bboxMax[0] - bboxMin[0])/(2f * Math.tan(camera.fieldOfView.getValue() / 2f)));
float ybasedZdist = (float)((bboxMax[1] - bboxMin[1])/(2f * Math.tan(camera.fieldOfView.getValue() / 2f)));
float zbasedZdist = (float)((bboxMax[2] - bboxMin[2])/(2f * Math.tan(camera.fieldOfView.getValue() / 2f)));
// In addition, need to move back half of the box depth, to situate this viewable
// region at the front side of the bbox. This makes the equations
float halfBoxDepth = (bboxMax[2] - bboxMin[2])/2f;
xbasedZdist += halfBoxDepth;
ybasedZdist += halfBoxDepth;
zbasedZdist += halfBoxDepth;
// Use the bigger of the two distances, with some slack for good measure:
if (xbasedZdist > ybasedZdist && xbasedZdist > zbasedZdist)
cameraPos[2] += (1f + backupSlack) * xbasedZdist;
else if ( ybasedZdist > zbasedZdist)
cameraPos[2] += (1f + backupSlack) * ybasedZdist;
else
cameraPos[2] += (1f + backupSlack) * zbasedZdist;
// If needed, transform position into camera parent space:
if (pathToCameraParent != null){
// Second argument means get matrix going from top to bottom of path.
float[] xfMat = MatUtil.getMatrixAlongPath(pathToCameraParent, false);
MatUtil.multVecMatrix(xfMat, cameraPos);
}
// Now set it the position in the camera
camera.position.setValue(cameraPos);
// Set the initial cameraHeading and cameraPitch from the current values in the camera.
Quaternion initCamQuat = new Quaternion();
initCamQuat.setAxisAngle(camera.orientation.getValue());
float[] headingPitchRoll = new float[3];
initCamQuat.getEulers(headingPitchRoll);
cameraHeading = headingPitchRoll[0];
cameraPitch = headingPitchRoll[1];
}
}